#pragma once

#include "../commons/code_generator.hpp"
#include "../commons/constants.hpp"
#include "../commons/utils.hpp"
#include "../lexer/lexercc.hpp"
#include "ast.hpp"
#include "ast_json_bind.hpp"
#include "my_parser.hpp"
#include "my_parser.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>

using std::fstream;
namespace fs = std::filesystem;

/**
 * Write matrix-based parser to target path
 * @param parser the parser
 * @param targetPath target directory to write
 * @param sourcePath source path of the parser/ directory
 * @param writeMain whether to write main function or not
 */
void writeParser(const MyParser &parser, const string &targetPath, string sourcePath,
                 bool writeMain, bool separate);

// private functions

static void toInitList(ostream &oss, const MyParser::RuleType &rule) {
    oss << "{";
    toInitList(oss, rule.name);
    oss << ", " << rule.index << ", " << rule.lhs << ", " << rule.rhs.size();
    oss << "}";
}

static void toInitList(ostream &oss, const MyParser::Action &action){
    oss << "{";
    switch(action.atype){
    case MyParser::ActionType::ACCEPT:
        oss << "true";
        break;
    case MyParser::ActionType::SHIFT:
        oss << action.state;
        break;
    case MyParser::ActionType::REDUCE:
        toInitList(oss, action.rule);
        break;
    default: // UNDEFINED
        break;
    }
    oss << "}";
}

static void writeParserHeader(ostream &oss) {
    oss << "/**\n * Parser generated by MyParser parser generator V1.0.0\n"
           " * Created at "
        << __DATE__ << " "
        << __TIME__
        "\n"
        " * Built by Zeege (R) with ♥\n"
        " * See https://gitee.com/zeege/my-parser-generator for details\n**/\n\n";
    oss << "#pragma once\n";
    oss << "#include \"parser_typedef_min.hpp\"\n";
    oss << "#include \"lexer.cpp\"\n";
    oss << "#include <iostream>\n";
    oss << "#include <iomanip>\n";
    oss << "#include <fstream>\n\n";
    oss << "using namespace std;\n\n";
    oss << "MyParser buildParser();\n\n";
}

static void writeParserDemo(ostream &oss) {
    oss << "/**\n * This is a demo for using the parser\n * Happy parsing!\n */\n";
    oss << "int main() {\n";
    oss << "\tMyLexer lexer = buildLexer();\n";
    oss << "\tMyParser parser = buildParser();\n";
    oss << "\tfstream sourceIn(\"path to source code to parse\");\n";
    oss << "\tfstream astConfigIn(\"path to AST config\");\n";
    oss << "\tfstream astOut(\"path to AST output\", fstream::trunc | fstream::out);\n";
    oss << "\tif(!sourceIn.is_open() || !astConfigIn.is_open() || !astOut.is_open()){\n";
    oss << "\t\tcout << \"Cannot open file.\\n\";\n\t\treturn 0;\n\t}\n";
    oss << "\tvector<Token> tokens = lexer.lex(sourceIn);\n";
    oss << "\tMyParser::PtrParseTreeNode parseTree = parser.parse(tokens);\n";
    oss << "\tASTConfig config = ASTConfig::configWithJson(astConfigIn);\n";
    oss << "\tPtrASTNode ast = ASTNode::buildAST(parseTree, config);\n";
    oss << "\t// You may also print AST in txt format, use: ast->printAST(astOut);\n";
    oss << "\tastOut << std::setw(2) << my_json_wrap(ast) << endl;\n";
    oss << "\treturn 0;\n}\n\n";
}

static void writeParserInternal(ostream &oss, const vector<string> &symbols,
                                const vector<vector<MyParser::Action>> &parseTab,
                                string targetPath) {
    oss << "MyParser buildParser() {\n";
    oss << "\tclock_t st = clock();\n";
    if(targetPath.empty()){
        oss << "\tfstream tableIn(\"./parse_table.csv\");\n";
        oss << "\tfstream syntaxIn(\"./syntax.csv\");\n";
        oss << "\tif(!tableIn.is_open() || !syntaxIn.is_open()) {\n";
        oss << "\t\tcout << \"Cannot open file.\\n\";\n\t\texit(0);\n\t}\n";
        oss << "\tMyParser parser;\n";
        oss << "\tparser.buildParserFromCsvParseTable(tableIn, syntaxIn);\n";
        oss << "\tclock_t ed = clock();\n";
        oss << "\tcout << \"Parser built in \" << ed - st << \" ms\\n\";\n";
        oss << "\treturn parser;\n}\n";
    } else {
        oss << "\tconst vector<string> _symbols = ";
        toInitList(oss, symbols);
        oss << ";\n";
        oss << "\tconst vector<vector<MyParser::Action>> _parseTable = ";
        toInitList(oss, parseTab);
        oss << ";\n";
        oss << "\tclock_t ed = clock();\n";
        oss << "\tcout << \"Parser built in \" << ed - st << \" ms\\n\";\n";
        oss << "\treturn MyParser(_symbols, _parseTable);\n}";
    }
}


void writeParser(const MyParser &parser, const string &targetPath, string sourcePath = "./", bool writeMain = true, bool separate = false){
    if(parser.hasConflict()){
        throw std::runtime_error("Parse table has conflict");
    }
    if (myEndsWith(targetPath, "/") && !fs::exists(targetPath)) {
        fs::create_directory(targetPath);
    }
    if(!fs::exists(sourcePath)){
        std::cerr << "[Error] Source path do no exists.\nProgram terminated.\n";
        throw std::runtime_error("Source path do no exists.");
    }
    separate = separate ||
        parser.getParseTable().size() * parser.getParseTable().at(0).size() > 500;
    if(separate){
        fs::remove(targetPath + "parse_table.csv");
        fs::remove(targetPath + "syntax.csv");
        fstream ptab;
        __OPEN_AND_CHECK(ptab, targetPath + "parse_table.csv", true);
        parser.printParseTable(ptab);
        ptab.close();
        __OPEN_AND_CHECK(ptab, targetPath + "syntax.csv", true);
        parser.printSyntax(ptab);
        ptab.close();
    }
    fs::remove(targetPath + "parser_typedef_min.hpp");
    fs::remove(targetPath + "parser.cpp");
    fs::copy_file(sourcePath + "/parser_typedef_min.hpp",
                  targetPath + "parser_typedef_min.hpp");
    fstream fss;
    __OPEN_AND_CHECK(fss, targetPath + "parser.cpp", true);
    writeParserHeader(fss);
    if(writeMain) {
        writeParserDemo(fss);
    }
    if(separate){
        writeParserInternal(fss, parser.getSymbols(), parser.getParseTable(), "");
    } else {
        writeParserInternal(fss, parser.getSymbols(), parser.getParseTable(), targetPath);
    }
    fss.close();
    cout << "++++ Parser created at " << targetPath << "/parser.cpp\n\n";
}
